Skip to content

feat: Implement ExportManifest RPC to reconstruct manifest from live state#1694

Merged
bjcoombs merged 1 commit intodevelopfrom
045-manifest-source-of-truth--14--export-manifest-rpc
Mar 16, 2026
Merged

feat: Implement ExportManifest RPC to reconstruct manifest from live state#1694
bjcoombs merged 1 commit intodevelopfrom
045-manifest-source-of-truth--14--export-manifest-rpc

Conversation

@bjcoombs
Copy link
Copy Markdown
Collaborator

Summary

  • Adds ExportManifest RPC to ManifestHistoryService for reconstructing a manifest from live service state
  • Enables migration of tenants configured via direct gRPC calls into the manifest-first workflow
  • Read-only operation: no mutations, no manifest version created

Changes Made

Proto (manifest_history_service.proto):

  • ExportManifestRequest with include_sections filter and optional manifest_version for fallback source
  • ExportManifestResponse with reconstructed Manifest, exported_at timestamp, SHA-256 checksum, section_sources provenance map, and warnings for partial failures

Export Service (export.go):

  • Collector interfaces (InstrumentCollector, AccountTypeCollector, SagaCollector, MarketDataCollector, OrganizationCollector, InternalAccountCollector, OperationalGatewayCollector) abstract downstream service queries
  • ExportCollectors struct groups optional collectors; nil collectors fall back to the stored manifest
  • Sections without live service APIs (valuation_rules, mappings, party_types, payment_rails) always source from fallback
  • Collector errors produce warnings and graceful fallback rather than failing the entire export

Handler (grpc_handler.go):

  • NewHistoryHandlerWithExport constructor injects the ExportService
  • ExportManifest RPC returns Unimplemented when no exporter is configured

Service registration (manifest_history.go):

  • ManifestHistoryServiceConfig.ExportCollectors field to wire up live collectors at startup

Testing

  • Unit tests for all collector paths: live success, live error with fallback, fallback-only sections
  • Tests for section filtering, checksum determinism, proto response conversion
  • All existing manifest package tests continue to pass

Task Master

Task 045-manifest-source-of-truth.14 - Implement ExportManifest RPC

Introduces an ExportManifest RPC on the ManifestHistoryService that
queries downstream services for their current resources and assembles
a complete Manifest proto. This enables migrating tenants configured
via direct gRPC calls into the manifest-first workflow.

Key design decisions:
- Collector interfaces per service section (instruments, account types,
  sagas, market data, organizations, internal accounts, operational
  gateway) allow the export to be assembled incrementally
- Sections without live service collectors (valuation_rules, mappings,
  party_types, payment_rails, seed_data) fall back to the last applied
  manifest version
- Collector errors produce warnings and graceful fallback rather than
  failing the entire export
- Response includes section_sources map documenting data provenance
  and SHA-256 checksum for content verification
- Read-only operation: no mutations, no manifest version created
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

Introduces a manifest export feature via new RPC method ExportManifest that reconstructs manifests from live service state with automatic fallback to stored versions. Includes protobuf definitions, Go service implementation with configurable collectors, comprehensive tests, and gRPC handler integration.

Changes

Cohort / File(s) Summary
Protocol Buffers
api/proto/meridian/control_plane/v1/manifest_history_service.proto
Adds read-only ExportManifest RPC to ManifestHistoryService with ExportManifestRequest and ExportManifestResponse messages to control scope and version sourcing.
Core Export Service
services/control-plane/internal/manifest/export.go
Implements ExportService orchestrator that reconstructs manifests from live collectors or falls back to stored versions. Defines SectionName constants, multiple collector interfaces, ExportCollectors aggregation struct, and per-section collection methods with checksum computation.
Export Service Tests
services/control-plane/internal/manifest/export_test.go
Comprehensive unit tests for ExportService covering section parsing, fallback behavior, live collector integration, error handling with warning propagation, and manifest content validation across all sections.
gRPC Handler
services/control-plane/internal/manifest/grpc_handler.go
Adds optional exporter field to HistoryHandler and introduces NewHistoryHandlerWithExport constructor. Implements ExportManifest RPC handler that delegates to exporter, maps version-not-found errors, and handles nil exporter gracefully.
Handler Tests
services/control-plane/internal/manifest/grpc_handler_test.go
Tests for nil exporter handling, successful handler construction with exporter, and error validation when history service is absent.
Service Integration
services/control-plane/service/manifest_history.go
Adds optional ExportCollectors field to ManifestHistoryServiceConfig. Conditionally creates exporter and uses NewHistoryHandlerWithExport when collectors are provided, else falls back to standard handler.

Sequence Diagram

sequenceDiagram
    participant Client
    participant HistoryHandler
    participant ExportService
    participant Collector
    participant HistoryService
    
    Client->>HistoryHandler: ExportManifest(includeSections, version)
    HistoryHandler->>ExportService: Export(includeSections, version)
    ExportService->>ExportService: parseSections(includeSections)
    ExportService->>HistoryService: GetManifestByVersion(version)
    HistoryService-->>ExportService: fallbackManifest
    ExportService->>Collector: ListInstruments/ListAccountTypes/etc
    alt Collector Success
        Collector-->>ExportService: live data
        ExportService->>ExportService: merge live data into manifest
    else Collector Failure
        ExportService->>ExportService: record warning, use fallback section
    end
    ExportService->>ExportService: manifestChecksum(manifest)
    ExportService-->>HistoryHandler: ExportResult
    HistoryHandler->>ExportService: ToProtoResponse()
    HistoryHandler-->>Client: ExportManifestResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Implement ExportManifest RPC to reconstruct manifest from live state' accurately and clearly summarizes the main feature addition, directly matching the primary change in the pull request.
Description check ✅ Passed The description comprehensively covers the changes across all modified files, explains the purpose and design rationale, and is directly related to the changeset content throughout.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 045-manifest-source-of-truth--14--export-manifest-rpc
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.

Change the reviews.profile setting to assertive to make CodeRabbit's nitpick more issues in your PRs.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve. One non-blocking suggestion on checksum determinism: protojson.Marshal is not guaranteed deterministic across builds or protobuf library versions. For this PRs scope (single-process content verification) this is fine. But if checksums are later stored and compared across service restarts/upgrades, consider switching to proto.MarshalOptions{Deterministic: true} with the binary wire format, which is guaranteed stable for identical schema+data. See summary comment for full review.

@claude
Copy link
Copy Markdown

claude Bot commented Mar 16, 2026

Claude Code Review

Commit: cf3af6e | CI: still running (most checks pending)

Summary

Clean implementation of the ExportManifest RPC. The collector interface pattern is well-designed: each downstream service gets a focused interface, nil collectors gracefully fall back to the stored manifest, and collector errors produce warnings rather than failing the whole export. The handler integration is minimal and correct -- the new constructor injects the exporter without disturbing existing RPCs.

This satisfies the requirement for task 045-manifest-source-of-truth.14: a read-only RPC that reconstructs a manifest from live service state for migration into the manifest-first workflow.

Risk Assessment

Area Level Detail
Blast radius Low New read-only RPC; existing RPCs untouched
Rollback Safe No migrations, no schema changes; revert removes the RPC
Scale Low Export is a migration tool, not a hot path; sequential collectors acceptable
Cross-system Low Queries downstream services read-only; errors produce warnings, not failures
Migration N/A No database migrations

Findings

Severity Location Description Status
Improvement export.go:manifestChecksum protojson.Marshal not guaranteed deterministic across builds/versions. Fine for single-process verification; if checksums are later stored and compared across restarts, switch to proto.MarshalOptions{Deterministic: true} with binary wire format Open
Note export.go Sequential collector execution is fine for migration use case Informational

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
services/control-plane/internal/manifest/export.go (1)

326-361: Consider partial success for multi-call collectors.

For collectMarketData, if ListMarketDataSources succeeds but ListMarketDataSets fails, the entire section falls back. This loses the successfully retrieved sources. The same pattern exists in collectOperationalGateway. This is a reasonable trade-off for consistency, but worth documenting if intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/control-plane/internal/manifest/export.go` around lines 326 - 361,
collectMarketData currently treats any error from ListMarketDataSources or
ListMarketDataSets as a full failure and falls back, which discards partial
successes; update collectMarketData to preserve successful sub-results (set
Sources if ListMarketDataSources succeeded, set Datasets if ListMarketDataSets
succeeded), aggregate and append a warning for the failing call(s) (using the
existing warning construction), set result.Manifest.MarketData to a
MarketDataConfig with whatever subfields succeeded, and annotate
result.SectionSources["market_data"] to reflect partial vs full live data (e.g.,
"live:market-information:partial" or similar) so callers can distinguish partial
success; apply the same partial-success pattern to collectOperationalGateway
where multiple List* methods are invoked.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@services/control-plane/internal/manifest/export.go`:
- Around line 326-361: collectMarketData currently treats any error from
ListMarketDataSources or ListMarketDataSets as a full failure and falls back,
which discards partial successes; update collectMarketData to preserve
successful sub-results (set Sources if ListMarketDataSources succeeded, set
Datasets if ListMarketDataSets succeeded), aggregate and append a warning for
the failing call(s) (using the existing warning construction), set
result.Manifest.MarketData to a MarketDataConfig with whatever subfields
succeeded, and annotate result.SectionSources["market_data"] to reflect partial
vs full live data (e.g., "live:market-information:partial" or similar) so
callers can distinguish partial success; apply the same partial-success pattern
to collectOperationalGateway where multiple List* methods are invoked.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 841c8e9a-50bb-4e17-94c1-de0cb583df2e

📥 Commits

Reviewing files that changed from the base of the PR and between 862f3fd and cf3af6e.

📒 Files selected for processing (6)
  • api/proto/meridian/control_plane/v1/manifest_history_service.proto
  • services/control-plane/internal/manifest/export.go
  • services/control-plane/internal/manifest/export_test.go
  • services/control-plane/internal/manifest/grpc_handler.go
  • services/control-plane/internal/manifest/grpc_handler_test.go
  • services/control-plane/service/manifest_history.go

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 55.89354% with 116 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
services/control-plane/internal/manifest/export.go 57.74% 95 Missing and 6 partials ⚠️
services/control-plane/service/manifest_history.go 0.00% 8 Missing ⚠️
...es/control-plane/internal/manifest/grpc_handler.go 56.25% 7 Missing ⚠️

📢 Thoughts on this report? Let us know!

@bjcoombs bjcoombs merged commit 94df1e0 into develop Mar 16, 2026
48 checks passed
@bjcoombs bjcoombs deleted the 045-manifest-source-of-truth--14--export-manifest-rpc branch March 16, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant